package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"gitee.com/zjzhang/log"
	"gitee.com/zjzhang/weixin"
	"github.com/graarh/golang-socketio"
	"github.com/graarh/golang-socketio/transport"
	"github.com/grokify/html-strip-tags-go"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"sync"
	"time"
)
const(
	//2 微信 3 WEB
	ACCESS_SOURCE="3"
	IMG_CONTEXT="<p><ocs_i href=\"%s%s\"/></p>"
	TXT_CONTEXT="<p>%s</p>"
	VOICE_CONTEXT="<p>%s<ocs_a href=\"%s%s\"/></p>"
	FACE_REG= `<\s*ocs_j\s+[^>]*?phrase\s*=\s*["'](.*?)["'][^>]*?/>`
	IMG_REG=`<\s*ocs_i\s+[^>]*?href\s*=\s*["'](.*?)["'][^>]*?/>`
)



type asrResponse struct {
	CorpusNo 	string				`json:"corpus_no,omitempty"`
	ErrMsg 		string				`json:"err_msg,omitempty"`
	ErrNo 		int					`json:"err_no,omitempty"`
	Result 		[]string			`json:"result,omitempty"`
	SN 			string				`json:"sn,omitempty"`
}
type ocsExt struct {
	IsLogin string `json:"isLogin,omitempty"`
}

type ocsModeRequest struct {
	Telephone			string				`json:"telephone,omitempty"`
	AccessSource 		string				`json:"accessSource,omitempty"`
	DirectTransMan		string				`json:"directtransman,omitempty"`
	IsForce				string				`json:"isForce,omitempty"`
	IsAnonymous			string				`json:"isAnonymous,omitempty"`
	AccessTypeDetail	string				`json:"accessTypeDetail,omitempty"`
	Client				string				`json:"client,omitempty"`
	Ext					ocsExt 				`json:"ext,omitempty"`
	ToMode				string 				`json:"toMode,omitempty"`
}

type oscProps map[string]string

type ocsModeRespnse struct {
	UserId				string				`json:"userId,omitempty"`
	//CurrentModeType 1 机器人 3转接中  2人工应答成功
	CurrentModeType		int					`json:"currentModeType,omitempty"`
	Name				string				`json:"name,omitempty"`
	MsgContent			string				`json:"msgContent,omitempty"`
	Props				oscProps			`json:"props,omitempty"`
}


type ocsMessage struct {
	Content				string				`json:"content"`
	Receiver			string				`json:"receiver"`
	AccessType			int					`json:"accessType"`
	Client				string				`json:"client"`
	Name				string				`json:"name"`
	ChatId				string				`json:"chatId,omitempty"`
	MsgId				string				`json:"msgId,omitempty"`
	Sender				string				`json:"sender,omitempty"`
	SenderType			int					`json:"senderType,omitempty"`
}


var (

	active = make(map[string]*userInfo)
	activeLock sync.Mutex
)



type userInfo struct {
	c  			*gosocketio.Client
	LastTime 	time.Time
	Form 		string
	ModeType	int
	LastMsgId	int64
	LastCreateTime int
}


func (u *userInfo) Close() {
	modeMsg:=&ocsModeRequest{Telephone:u.Form,AccessSource:ACCESS_SOURCE,IsForce:"0",ToMode:"2",AccessTypeDetail:"8",Ext:ocsExt{IsLogin:"1"},Client:"web"}
	strbuf,_:=json.Marshal(modeMsg)
	log.Debug("-->ocs_mode",string(strbuf))
	if err:=u.c.Emit("ocs_mode",modeMsg);err!=nil{
		log.Debug(err)
	}
	u.c.Close()
	activeLock.Lock()
	delete(active, u.Form)
	activeLock.Unlock()
}
func (u *userInfo) SendCustEvent(msg *weixin.Message) (err error){
	err=nil
	u.LastTime=time.Now()
	u.LastMsgId=msg.MsgId
	lastCreateTime ,_:=strconv.Atoi(msg.CreateTime)
	if lastCreateTime<u.LastCreateTime {
		return
	}
	u.LastCreateTime=lastCreateTime
	switch u.ModeType{
	case 1:
		modeMsg:=&ocsModeRequest{Telephone:u.Form,AccessSource:ACCESS_SOURCE,IsForce:"0",ToMode:"1",AccessTypeDetail:"8",Ext:ocsExt{IsLogin:"1"},Client:"web"}
		strbuf,_:=json.Marshal(modeMsg)
		log.Debug("-->ocs_mode",string(strbuf))
		if err=u.c.Emit("ocs_mode",modeMsg);err!=nil{
			log.Debug(err)
		}
		break
	default :

		modeMsg:=&ocsModeRequest{Telephone:u.Form,AccessSource:ACCESS_SOURCE,IsForce:"0",ToMode:"2",AccessTypeDetail:"8",Ext:ocsExt{IsLogin:"1"},Client:"web"}
		strbuf,_:=json.Marshal(modeMsg)
		log.Debug("-->ocs_mode",string(strbuf))
		if err=u.c.Emit("ocs_mode",modeMsg);err!=nil{
			log.Debug(err)
		}
		break
		
	
	}

	return
}

func (u *userInfo) SendCustMessge(msg *weixin.Message) (err error){
	err=nil
	u.LastTime=time.Now()
	u.LastMsgId=msg.MsgId
	lastCreateTime ,_:=strconv.Atoi(msg.CreateTime)
	if lastCreateTime<u.LastCreateTime {
		return
	}
	u.LastCreateTime=lastCreateTime
	switch msg.MsgType {
	case weixin.MsgTypeText:
		err=u.SendTextMsg(weixin.NewRecvText(msg))
		break
	case weixin.MsgTypeImage:
		err=u.SendImageMsg(weixin.NewRecvImage(msg))
		break
	case weixin.MsgTypeVoice:
		err=u.SendVoiceMsg(weixin.NewRecvVoice(msg))
		break
	case weixin.MsgTypeVideo:
		break
	case weixin.MsgTypeShortVideo:
		break
	case weixin.MsgTypeLocation:
		break
	case weixin.MsgTypeLink:
		break
	default:
		log.Errorf("unexpected receive MsgType: %s", msg.MsgType)
		return
	}

	return
}
func (u *userInfo)SendTextMsg(m *weixin.RecvText)  (err error) {
	parser := NewWeichatParser()
	replased := parser.ToHtmlImages(m.Content)
	log.Debug("转移后消息:",replased)
	err=nil
	txtMsg:=&ocsMessage{Client:"client",AccessType:3,Content:fmt.Sprintf(TXT_CONTEXT,replased)}
	if err=u.c.Emit("ocs_message",txtMsg);err!=nil{
		log.Debug(err)
	}
	return
}

func (u *userInfo)SendVoiceMsg(m *weixin.RecvVoice)(err error) {

	//io.Pipe()
	filename,content, err:=weixin.GetTemporaryMaterial(m.MediaId);
	if err!=nil {
		log.Error("微信语音文件获取失败",err)
		return
	}

	//保存文件
	fullfilename:="./tmp/"+filename
	file, err := os.Create(fullfilename)
	defer file.Close()
	if err != nil {
		log.Error("文件创建失败",err)
	}
	_, err = file.Write(content)
	if err != nil {
		log.Error("文件写入失败",err)
		return  err
	}

	go func() {
		//后台上传文件并获取识别结果
		file, err := os.Open(fullfilename)
		if err != nil {
			return
		}
		defer file.Close()
		resp, _ := client.Post(Config.BaiduASR, "audio/amr;rate=8000", bufio.NewReader(file))
		js, _ := ioutil.ReadAll(resp.Body)
		asr := &asrResponse{}
		json.Unmarshal(js, asr)
		log.Debugf("识别语音结果  %+v ", asr)

		if len(asr.Result) > 0 {
			msg := &ocsMessage{Client: "client", AccessType: 3, Content: fmt.Sprintf(VOICE_CONTEXT, asr.Result[0])}
			if err = u.c.Emit("ocs_message", msg); err != nil {
				log.Debug(err)
			}
		}
	}()

	cmd := exec.Command("ffmpeg","-i",fullfilename,fullfilename+".wav")
	log.Debug(cmd.Dir)
	log.Debug(cmd.Args)
	err=cmd.Run()
	if err != nil {
		log.Error("转码指令执行失败",err)
		return  err
	}
	file.Close()

	file, err = os.Open(fullfilename+".wav")
	if err != nil {
		log.Error("转码后的文件打开失败",err)
		return err
	}
	defer file.Close()
	buf,err:=weixin.UploadMultipart(Config.UploadURL,"upfile",file,nil)
	log.Debug("fastDFS",buf)



	return
}

func (u *userInfo)SendImageMsg(m *weixin.RecvImage)  (err error) {
	err=nil
	filename,content, err:=weixin.GetTemporaryMaterial(m.MediaId);
	if err!=nil {
		log.Error(err)
		return
	}

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(Config.UploadParam, filename)
	if err != nil {
		log.Error(err)
		return err
	}
	part.Write(content)
	err = writer.Close()
	if err != nil {
		log.Error(err)
		return err
	}

	req, err := http.NewRequest("POST", Config.UploadURL, body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Error("文件上传失败",filename,err)
		return
	} else {
		_, err = body.ReadFrom(resp.Body)
		if err != nil {
			log.Error(err)
			return
		}
		resp.Body.Close()
		log.Debug(resp.StatusCode)
		log.Debug(resp.Header)
		log.Debug(body)
	}
	msg:=&ocsMessage{Client:"client",AccessType:3,Content:fmt.Sprintf(IMG_CONTEXT,Config.UploadPrefix,body)}
	if err=u.c.Emit("ocs_message",msg);err!=nil{
		log.Debug(err)
	}
	return
}

func newUser(from string) (u *userInfo,err error){
	log.Debug("创建连接",from)
	u=&userInfo{Form: from,ModeType:1}
	u.c, err = gosocketio.Dial(Config.SocketIoURL, transport.GetDefaultWebsocketTransport())
	//log.Debug(u,err)

	//处理聊天消息
	u.c.On("ocs_message",func(sio *gosocketio.Channel, msg ocsMessage) {
		u.LastTime=time.Now()
		log.Debugf("ocs_message  %+v",msg)
		//处理图消息
		imageReg:=regexp.MustCompile(IMG_REG)
		substr:=imageReg.FindAllStringSubmatch(msg.Content,-1)
		for _,v:=range substr {
			log.Debug("图片地址",v[1])
			go func(url string) {
				mediaId , _ , err :=weixin.UploadTemporaryMaterialByURL(weixin.MediaTypeImage,url)
				if err!=nil{
					log.Error(err)
					return
				}
				m := &weixin.CustImage{MediaId: mediaId }
				weixin.SendCustomMsg(u.Form, m)
			}(v[1])

		}
		imageReg.ReplaceAllStringFunc(msg.Content, func(s string) string {
			return ""
		})
		//处理表情消息

		faceReg :=regexp.MustCompile(FACE_REG)
		msgStr:=faceReg.ReplaceAllString(msg.Content,"${1}")
		//处理所有HTML标签
		content:=strip.StripTags(msgStr)
		m := &weixin.CustText{Content: content }
		weixin.SendCustomMsg(u.Form, m)
	})
	//处理协议消息
	u.c.On("ocs_mode",func(sio *gosocketio.Channel, msg ocsModeRespnse) {
		u.LastTime=time.Now()
		log.Debugf("<--ocs_mode  %+v",msg)
		u.ModeType= msg.CurrentModeType
		content,_ := strconv.Unquote("\""+msg.MsgContent+"\"")
		m := &weixin.CustText{Content: content}
		weixin.SendCustomMsg(u.Form, m)
	})
	u.c.On(gosocketio.OnDisconnection, func(sio *gosocketio.Channel) {
		log.Debug("关闭连接",u.Form)
		activeLock.Lock()
		delete(active,u.Form)
		activeLock.Unlock()
	})
	u.c.On(gosocketio.OnConnection, func(sio *gosocketio.Channel) {
		log.Debug("连接成功",u.Form,sio.Id())
	})


	connMsg:=&ocsModeRequest{
		Telephone:from,
		AccessSource:ACCESS_SOURCE,
		DirectTransMan:"0",
		IsForce:"0",
		IsAnonymous:"0",
		AccessTypeDetail:"8",
		Client:"web",
		Ext:ocsExt{IsLogin:"1"}}
	strbuf,_:=json.Marshal(connMsg)
	log.Debug("ocs_connect",string(strbuf))
	if err=u.c.Emit("ocs_connect",connMsg);err!=nil{
		log.Debug(err)
	}

	return
}

func SendCustEvent(msg *weixin.Message) (process bool, err error) {
	process = false
	err = nil
	if msg.EventKey != `/客服` {
		return
	}
	process = true
	u, ok := active[msg.FromUserName]
	if ok {
		err = u.SendCustEvent(msg)
	} else {
		u, err = newUser(msg.FromUserName)
		active[msg.FromUserName] = u
		u.SendCustEvent(msg)
	}
	return
}

func SendCustMessge(msg *weixin.Message) (err error){

	err = nil
	u, ok := active[msg.FromUserName]
	if ok {
		err=u.SendCustMessge(msg)
	}else{
		u,err =newUser(msg.FromUserName)
		active[msg.FromUserName]=u
		u.SendCustMessge(msg)
	}
	return
}

func init() {

	log.Debug("客服微信桥接系统启动")
	go func() {
		ticker := time.NewTicker(10 * time.Second) // --- A
		defer ticker.Stop()
		for {
			for _, v := range active {
				if time.Now().Sub(v.LastTime).Seconds() > 300 {
					v.Close()
				}
			}
			<-ticker.C
		}
	}()
}
